﻿using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Diagnostics;
using System.Linq;
using System.Text;
using Microsoft.VisualStudio.Debugger.Interop;
using UDKDevKitVSX.Concrete;
using System.IO;

namespace UDKDevKitVSXDebugEngine.Classes
{
    public class ExecSimulator
    {
        private Engine engine;

        public ExecSimulator(Engine engine)
        {
            if (engine == null)
            {
                Debug.Fail("ArgumentNullException - engine");
                throw new ArgumentNullException("engine");
            }

            this.engine = engine;
        }

        internal EngineCallback Callback
        {
            get { return this.engine.Callback; }
        }

        internal Engine Engine
        {
            get { return this.engine; }
        }
        
        internal void RaiseDebugEventsOnResumeProcess()
        {
            Debug.WriteLine("Raise: OnLoadComplete");

            // ASD: from DispatchDebugEvent we need to run m_callback->OnLoadComplete(thread);
            this.Callback.OnLoadComplete();
        }

        internal void RaiseDebugEventsOnContinue()
        {
            Debug.WriteLine("Raise: OnEntryPoint");

            this.Callback.OnEntryPoint();
        }

        internal void RaiseDebugEventsBreakpoint()
        {
            Debug.WriteLine("Raise: OnBreakpoint");

            // we need to check if stops happend on break point
            if (this.Engine.IsContinueOnGo)
            {
                // we need to find all bound breakpoints
                this.Callback.OnBreakpoint(
                    this.Engine.FindAllBoundExceptions(),
                    this.Engine.DebugContext.DocumentContext.CurrentDebuggingStatementLine);
            }
            else
            {
                this.Callback.OnBreakpoint(
                    null,
                    this.Engine.DebugContext.DocumentContext.CurrentDebuggingStatementLine);
            }
        }

        internal void RaiseDebugEventsOnTerminateProcess()
        {
            Debug.WriteLine("Raise: OnTerminateProcess");

            this.Callback.OnProcessExit(0);
        }

        internal void LoadDocumentFile(string className)
        {
            if (String.IsNullOrEmpty(className))
            {
                Debug.Fail("ArgumentException - className");
                throw new ArgumentException("className");
            }

            string fileName = 
                String.Format(
                    "{0}\\{1}.uc", 
                    this.Engine.SourcePath, 
                    className.Trim().Replace(".", "\\Classes\\"));

            this.Engine.DebugContext.DocumentContext = DocumentContext.Create(fileName, className);
        }

        internal void SetStatementLine(string lineNumber)
        {
            if (String.IsNullOrEmpty(lineNumber))
            {
                Debug.Fail("ArgumentException - lineNumber");
                throw new ArgumentException("lineNumber");
            }

            this.Engine.DebugContext.DocumentContext.CurrentDebuggingStatementLine = uint.Parse(lineNumber) - 1;
        }

        internal void RaiseDebugEventsOnBreakpointBound(PendingBreakpoint pendingBreakpoint)
        {
            if (pendingBreakpoint == null)
            {
                Debug.Fail("ArgumentException - pendingBreakpoint");
                throw new ArgumentException("pendingBreakpoint");
            }

            this.Callback.OnBreakpointBound(
                pendingBreakpoint.CreateBoundBreakpoints(this.engine));
        }

        internal void AddWatches(string watches)
        {
            var watch = -1;
            var firstLevel = new List<VariableInformation>();

            // get watch type
            string data = watches.TrimStart();

            var pos = data.IndexOf('\t');
            if (pos != -1)
            {
                watch = Int32.Parse(data.Substring(0, pos).Trim());

                var dataVars = data.Substring(pos + 1);
                var dataVarArrays = dataVars.Split('\t');

                var hierarhy = new Dictionary<int, VariableInformation>();

                int index = 0;
                for (int i = 0; i < dataVarArrays.Length; i += 3)
                {
                    int parentIndex = Int32.Parse(dataVarArrays[i]);
                    string name = dataVarArrays[i + 1];
                    string value = dataVarArrays[i + 2];

                    var variable = new VariableInformation();
                    variable.Name = name;
                    variable.Value = value;

                    hierarhy[index++] = variable;

                    if (parentIndex > 0)
                    {
                        hierarhy[parentIndex - 1].AddChildren(variable);
                    }
                    else
                    {
                        firstLevel.Add(variable);
                    }
                }
            }
            else
            {
                if (!Int32.TryParse(data, out watch))
                {
                    throw new ArgumentException("Incorrect watch value: " + data);
                }
            }

            switch (watch)
            {
                case 0: this.Engine.DebugContext.Locals = firstLevel;
                    break;
                case 1: this.Engine.DebugContext.Globals = firstLevel;
                    break;
                case 2: this.Engine.DebugContext.Watches = firstLevel;
                    break;
            }
        }

        internal void CallStackClear()
        {
            if (this.Engine.DebugContext.CallStacks == null)
            {
                return;
            }

            this.Engine.DebugContext.CallStacks.Clear();
        }

        internal void CallStackAdd(string stackName)
        {
            if (this.Engine.DebugContext.CallStacks == null)
            {
                this.Engine.DebugContext.CallStacks = new List<string>();
            }

            this.Engine.DebugContext.CallStacks.Add(stackName);
        }

        internal void RaiseDebugEventsExpressionEvaluationComplete(DebuggerExpression exp)
        {
            this.Engine.Callback.OnDebugExpressionEvaluationComplete(exp);
        }
    }
}
